using System;
using CodePlex.TfsLibrary.ObjectModel;
using CodePlex.UnitTestUtility;
using Xunit;

namespace CodePlex.TfsLibrary.ClientEngine
{
    public class TfsEngineCommitTest
    {
        [Fact]
        public void CallbackCalledForDelete()
        {
            SourceItem sourceItem = null;
            TestableTfsEngine engine = TestableTfsEngine.Create();
            engine.FileSystem.EnsurePath(@"C:\Foo");
            engine.TfsState.TrackFolder("http://tfsUrl", "$/Foo", @"C:\Foo", 1, 2, SourceItemStatus.Unmodified);
            engine.TfsState.TrackFile(@"C:\Foo\bar.txt", 3, 4, SourceItemStatus.Delete);

            engine.Commit(@"C:\Foo", "message", delegate(SourceItem item,
                                                         SourceItemResult result)
                                                {
                                                    sourceItem = item;
                                                });

            Assert.Equal(SourceItemStatus.Delete, sourceItem.LocalItemStatus);
        }

        [Fact]
        public void CallbackCalledForUpload()
        {
            bool callbackCalled = false;
            TestableTfsEngine engine = TestableTfsEngine.Create();
            engine.FileSystem.EnsurePath(@"C:\Foo");
            engine.FileSystem.WriteAllText(@"C:\Foo\bar.txt", "text");
            engine.TfsState.TrackFolder("http://tfsUrl", "$/Foo", @"C:\Foo", 1, 2, SourceItemStatus.Unmodified);
            engine.TfsState.TrackFile(@"C:\Foo\bar.txt", 3, 4, SourceItemStatus.Add);

            engine.Commit(@"C:\Foo", "message", delegate
                                                {
                                                    callbackCalled = true;
                                                });

            Assert.True(callbackCalled);
        }

        [Fact]
        public void CommitCalled()
        {
            TestableTfsEngine engine = TestableTfsEngine.Create();
            engine.FileSystem.EnsurePath(@"C:\Foo");
            engine.FileSystem.WriteAllText(@"C:\Foo\bar.txt", "text");
            engine.TfsState.TrackFolder("http://tfsUrl", "$/Foo", @"C:\Foo", 1, 2, SourceItemStatus.Unmodified);
            engine.TfsState.TrackFile(@"C:\Foo\bar.txt", 3, 4, SourceItemStatus.Add);

            engine.Commit(@"C:\Foo", "message", null);

            Assert.Equal("http://tfsUrl", engine.SourceControlService.Commit_TfsUrl);
            Assert.Equal(engine.SourceControlService.CreateWorkspace_WorkspaceName, engine.SourceControlService.Commit_WorkspaceName);
            Assert.Equal("message", engine.SourceControlService.Commit_Comment);
            Assert.Equal(1, engine.SourceControlService.Commit_ServerItems.Count);
            Assert.Equal("$/Foo/bar.txt", engine.SourceControlService.Commit_ServerItems[0]);
        }

        [Fact]
        public void ConflictedFile()
        {
            TestableTfsEngine engine = TestableTfsEngine.Create();
            engine.FileSystem.EnsurePath(@"C:\Foo");
            engine.FileSystem.WriteAllText(@"C:\Foo\bar.txt", "text");
            engine.TfsState.TrackFolder("http://tfsUrl", "$/Foo", @"C:\Foo", 1, 2, SourceItemStatus.Unmodified);
            engine.TfsState.TrackFile(@"C:\Foo\bar.txt", 3, 4, SourceItemStatus.Unmodified);
            engine.TfsState.MarkFileAsConflicted(@"C:\Foo\bar.txt", @"C:\Foo\bar.txt", 5);

            Assert.Throws<ConflictedCommitException>(delegate
                                                     {
                                                         engine.Commit(@"C:\Foo", "message", null);
                                                     });
        }

        [Fact]
        public void DeleteWorkspaceCalled()
        {
            StubSourceControlService stub;
            TestableTfsEngine engine = TestableTfsEngine.Create();
            stub = engine.SourceControlService;
            engine.FileSystem.EnsurePath(@"C:\Foo");
            engine.FileSystem.WriteAllText(@"C:\Foo\bar.txt", "text");
            engine.TfsState.TrackFolder("http://tfsUrl", "$/Foo", @"C:\Foo", 1, 2, SourceItemStatus.Unmodified);
            engine.TfsState.TrackFile(@"C:\Foo\bar.txt", 3, 4, SourceItemStatus.Add);

            engine.Commit(@"C:\Foo", "message", null);

            Assert.Equal("http://tfsUrl", stub.DeleteWorkspace_TfsUrl);
            Assert.Equal(stub.CreateWorkspace_WorkspaceName, stub.DeleteWorkspace_WorkspaceName);
        }

        [Fact]
        public void DoNotUploadDeletedFile()
        {
            TestableTfsEngine engine = TestableTfsEngine.Create();
            engine.FileSystem.EnsurePath(@"C:\Foo");
            engine.TfsState.TrackFolder("http://tfsUrl", "$/Foo", @"C:\Foo", 1, 2, SourceItemStatus.Unmodified);
            engine.TfsState.TrackFile(@"C:\Foo\bar.txt", 3, 4, SourceItemStatus.Delete);

            engine.Commit(@"C:\Foo", "message", null);

            Assert.Null(engine.SourceControlService.UploadFile_TfsUrl);
            Assert.Null(engine.SourceControlService.UploadFile_Credentials);
            Assert.Null(engine.SourceControlService.UploadFile_WorkspaceName);
            Assert.Null(engine.SourceControlService.UploadFile_LocalPath);
            Assert.Null(engine.SourceControlService.UploadFile_ServerPath);
        }

        [Fact]
        public void EmptyFoldersRemoved()
        {
            TestableTfsEngine engine = TestableTfsEngine.Create();
            engine.FileSystem.EnsurePath(@"C:\Foo");
            engine.FileSystem.EnsurePath(@"C:\Foo\Empty");
            engine.FileSystem.EnsurePath(@"C:\Foo\NotEmpty");
            engine.FileSystem.WriteAllText(@"C:\Foo\NotEmpty\bar.txt", "text");
            engine.TfsState.TrackFolder("http://tfsUrl", "$/Foo", @"C:\Foo", 1, 2, SourceItemStatus.Unmodified);
            engine.TfsState.TrackFolder("http://tfsUrl", "$/Foo/Empty", @"C:\Foo\Empty", 3, 4, SourceItemStatus.Unmodified);
            engine.TfsState.TrackFolder("http://tfsUrl", "$/Foo/Empty", @"C:\Foo\Empty", 3, 4, SourceItemStatus.Delete);
            engine.TfsState.TrackFolder("http://tfsUrl", "$/Foo/NotEmpty", @"C:\Foo\NotEmpty", 5, 6, SourceItemStatus.Unmodified);
            engine.TfsState.TrackFolder("http://tfsUrl", "$/Foo/NotEmpty", @"C:\Foo\NotEmpty", 5, 6, SourceItemStatus.Delete);

            engine.Commit(@"C:\Foo", "message", null);

            Assert.False(engine.TfsState.IsFolderTracked(@"C:\Foo\Empty"));
            Assert.False(engine.TfsState.IsFolderTracked(@"C:\Foo\NotEmpty"));
            Assert.False(engine.FileSystem.DirectoryExists(@"C:\Foo\Empty"));
            Assert.True(engine.FileSystem.DirectoryExists(@"C:\Foo\NotEmpty"));
            Assert.True(engine.FileSystem.FileExists(@"C:\Foo\NotEmpty\bar.txt"));
        }

        [Fact]
        public void FolderMarkedAsDeletedAndThenPhysicallyDeletedDoesNotThrow()
        {
            TestableTfsEngine engine = TestableTfsEngine.Create();
            engine.FileSystem.EnsurePath(@"C:\Foo");
            engine.FileSystem.EnsurePath(@"C:\Foo\Bar");
            engine.TfsState.TrackFolder("http://tfsUrl", "$/Foo", @"C:\Foo", 1, 2, SourceItemStatus.Unmodified);
            engine.TfsState.TrackFolder("http://tfsUrl", "$/Foo/Bar", @"C:\Foo\Bar", 3, 4, SourceItemStatus.Unmodified);
            engine.TfsState.TrackFolder("http://tfsUrl", "$/Foo/Bar", @"C:\Foo\Bar", 3, 4, SourceItemStatus.Delete);
            engine.FileSystem.DeleteDirectory(@"C:\Foo\Bar", true);

            engine.Commit(@"C:\Foo", "message", null);
        }

        [Fact]
        public void ItemsAreUpdatedWithNewChangesetId()
        {
            TestableTfsEngine engine = TestableTfsEngine.Create();
            engine.FileSystem.EnsurePath(@"C:\Foo");
            engine.FileSystem.EnsurePath(@"C:\Foo\Bar");
            engine.FileSystem.EnsurePath(@"C:\Foo\Delete");
            engine.FileSystem.WriteAllText(@"C:\Foo\Bar\added-file.txt", "new file");
            engine.FileSystem.WriteAllText(@"C:\Foo\bar.txt", "text");
            engine.TfsState.TrackFolder("http://tfsUrl", "$/Foo", @"C:\Foo", 1, 2, SourceItemStatus.Unmodified);
            engine.TfsState.TrackFolder("http://tfsUrl", "$/Foo/Bar", @"C:\Foo\Bar", 0, 0, SourceItemStatus.Add);
            engine.TfsState.TrackFolder("http://tfsUrl", "$/Foo/Delete", @"C:\Foo\Delete", 7, 8, SourceItemStatus.Unmodified);
            engine.TfsState.TrackFolder("http://tfsUrl", "$/Foo/Delete", @"C:\Foo\Delete", 7, 8, SourceItemStatus.Delete);
            engine.TfsState.TrackFile(@"C:\Foo\bar.txt", 3, 4, SourceItemStatus.Unmodified);
            engine.TfsState.TrackFile(@"C:\Foo\Bar\added-file.txt", 0, 0, SourceItemStatus.Add);
            engine.TfsState.TrackFile(@"C:\Foo\deleted.txt", 12, 14, SourceItemStatus.Delete);
            engine.FileSystem.WriteAllText(@"C:\Foo\bar.txt", "mod text");

            engine.Commit(@"C:\Foo", "message", null);

            int changesetId = engine.SourceControlService.Commit__Result;

            SourceItem fooBarSourceItem = engine.TfsState.GetSourceItem(@"C:\Foo\Bar");
            Assert.Equal(changesetId, fooBarSourceItem.LocalChangesetId);
            Assert.Equal(SourceItemStatus.Unmodified, fooBarSourceItem.LocalItemStatus);

            SourceItem barTxtSourceItem = engine.TfsState.GetSourceItem(@"C:\Foo\bar.txt");
            Assert.Equal(changesetId, barTxtSourceItem.LocalChangesetId);
            Assert.Equal(SourceItemStatus.Unmodified, barTxtSourceItem.LocalItemStatus);

            SourceItem addedFileSourceItem = engine.TfsState.GetSourceItem(@"C:\Foo\Bar\added-file.txt");
            Assert.Equal(changesetId, addedFileSourceItem.LocalChangesetId);
            Assert.Equal(SourceItemStatus.Unmodified, addedFileSourceItem.LocalItemStatus);

            Assert.False(engine.TfsState.IsFolderTracked(@"C:\Foo\Delete"));
            Assert.False(engine.TfsState.IsFileTracked(@"C:\Foo\deleted.txt"));
        }

        [Fact]
        public void LocalFileDeletedIfExists()
        {
            TestableTfsEngine engine = TestableTfsEngine.Create();
            engine.FileSystem.EnsurePath(@"C:\Foo");
            engine.FileSystem.WriteAllText(@"C:\Foo\bar.txt", "text");
            engine.TfsState.TrackFolder("http://tfsUrl", "$/Foo", @"C:\Foo", 1, 2, SourceItemStatus.Unmodified);
            engine.TfsState.TrackFile(@"C:\Foo\bar.txt", 3, 4, SourceItemStatus.Delete);

            engine.Commit(@"C:\Foo", "message", null);

            Assert.False(engine.FileSystem.FileExists(@"C:\Foo\bar.txt"));
        }

        [Fact]
        public void NonDeletedItemsInDeletedFolderPreventsCommmit()
        {
            TestableTfsEngine engine = TestableTfsEngine.Create();
            engine.FileSystem.EnsurePath(@"C:\Foo");
            engine.FileSystem.EnsurePath(@"C:\Foo\Bar");
            engine.TfsState.TrackFolder("http://tfsUrl", "$/Foo", @"C:\Foo", 1, 2, SourceItemStatus.Unmodified);
            engine.TfsState.TrackFolder("http://tfsUrl", "$/Foo/Bar", @"C:\Foo\Bar", 1, 2, SourceItemStatus.Delete);
            engine.FileSystem.WriteAllText(@"C:\Foo\Bar\boo.txt", "unmodified file");
            engine.TfsState.TrackFile(@"C:\Foo\Bar\boo.txt", 3, 4, SourceItemStatus.Unmodified);

            Assert.Throws<InconsistentTfsStateException>(delegate
                                                         {
                                                             engine.Commit(@"C:\Foo", "whatever", delegate {});
                                                         });
        }

        [Fact]
        public void NullLocalPath()
        {
            TestableTfsEngine engine = TestableTfsEngine.Create();

            Assert.Throws<ArgumentNullException>(delegate
                                                 {
                                                     engine.Commit(null, "", null);
                                                 });
        }

        [Fact]
        public void NullMessage()
        {
            TestableTfsEngine engine = TestableTfsEngine.Create();

            Assert.Throws<ArgumentNullException>(delegate
                                                 {
                                                     engine.Commit(@"C:\Foo", null, null);
                                                 });
        }

        [Fact]
        public void QueryItemsCalledForAddedFile()
        {
            TestableTfsEngine engine = TestableTfsEngine.Create();
            engine.SourceControlService.QueryItems__Result = new SourceItem[] { engine.SourceControlService._SingleFile[1] };
            engine.FileSystem.EnsurePath(@"C:\Foo");
            engine.FileSystem.WriteAllText(@"C:\Foo\file1.txt", "text");
            engine.TfsState.TrackFolder("http://tfsUrl", "$/Foo", @"C:\Foo", 1, 2, SourceItemStatus.Unmodified);
            engine.TfsState.TrackFile(@"C:\Foo\file1.txt", 3, 4, SourceItemStatus.Add);

            engine.Commit(@"C:\Foo", "message", null);

            Assert.Equal("http://tfsUrl", engine.SourceControlService.QueryItems_TfsUrl);
            Assert.Equal("$/Foo/file1.txt", engine.SourceControlService.QueryItems_ServerPath);
            Assert.Equal(engine.SourceControlService._SingleFile[1].ItemId, engine.TfsState.GetSourceItem(@"C:\Foo\file1.txt").ItemId);
        }

        [Fact]
        public void QueryItemsCalledForAddedFolder()
        {
            TestableTfsEngine engine = TestableTfsEngine.Create();
            engine.SourceControlService.QueryItems__Result = new SourceItem[] { engine.SourceControlService._SingleDirectory[1] };
            engine.FileSystem.EnsurePath(@"C:\Foo");
            engine.FileSystem.EnsurePath(@"C:\Foo\folder");
            engine.TfsState.TrackFolder("http://tfsUrl", "$/Foo", @"C:\Foo", 1, 2, SourceItemStatus.Unmodified);
            engine.TfsState.TrackFolder("http://tfsUrl", "$/Foo/folder", @"C:\Foo\folder", 0, 0, SourceItemStatus.Add);

            engine.Commit(@"C:\Foo", "message", null);

            Assert.Equal("http://tfsUrl", engine.SourceControlService.QueryItems_TfsUrl);
            Assert.Equal("$/Foo/folder", engine.SourceControlService.QueryItems_ServerPath);
            Assert.Equal(engine.SourceControlService._SingleDirectory[1].ItemId, engine.TfsState.GetSourceItem(@"C:\Foo\folder").ItemId);
        }

        [Fact(Skip="Potential future optimization")]
        public void QueryItemsCalledOnceForAllAddedItems() {}

        [Fact]
        public void ShortCircuitIfThereIsNothingToDo()
        {
            TestableTfsEngine engine = TestableTfsEngine.Create();
            engine.FileSystem.EnsurePath(@"C:\Foo");
            engine.TfsState.TrackFolder("http://tfsUrl", "$/Foo", @"C:\Foo", 1, 2, SourceItemStatus.Unmodified);

            engine.Commit(@"C:\Foo", "message", null);

            Assert.Null(engine.SourceControlService.CreateWorkspace_WorkspaceName);
        }

        [Fact]
        public void UploadAddedFile()
        {
            TestableTfsEngine engine = TestableTfsEngine.Create();
            engine.FileSystem.EnsurePath(@"C:\Foo");
            engine.FileSystem.WriteAllText(@"C:\Foo\bar.txt", "text");
            engine.TfsState.TrackFolder("http://tfsUrl", "$/Foo", @"C:\Foo", 1, 2, SourceItemStatus.Unmodified);
            engine.TfsState.TrackFile(@"C:\Foo\bar.txt", 3, 4, SourceItemStatus.Add);

            engine.Commit(@"C:\Foo", "message", null);

            Assert.Equal("http://tfsUrl", engine.SourceControlService.UploadFile_TfsUrl);
            Assert.NotNull(engine.SourceControlService.UploadFile_WorkspaceName);
            Assert.Equal(engine.SourceControlService.CreateWorkspace_WorkspaceName, engine.SourceControlService.UploadFile_WorkspaceName);
            Assert.Equal(@"C:\Foo\bar.txt", engine.SourceControlService.UploadFile_LocalPath);
            Assert.Equal("$/Foo/bar.txt", engine.SourceControlService.UploadFile_ServerPath);
        }

        [Fact]
        public void UploadModifiedFile()
        {
            TestableTfsEngine engine = TestableTfsEngine.Create();
            engine.FileSystem.EnsurePath(@"C:\Foo");
            engine.FileSystem.WriteAllText(@"C:\Foo\bar.txt", "text");
            engine.TfsState.TrackFolder("http://tfsUrl", "$/Foo", @"C:\Foo", 1, 2, SourceItemStatus.Unmodified);
            engine.TfsState.TrackFile(@"C:\Foo\bar.txt", 3, 4, SourceItemStatus.Unmodified);
            engine.FileSystem.WriteAllText(@"C:\Foo\bar.txt", "mod text");

            engine.Commit(@"C:\Foo", "message", null);

            Assert.Equal(@"C:\Foo\bar.txt", engine.SourceControlService.UploadFile_LocalPath);
            Assert.Equal("$/Foo/bar.txt", engine.SourceControlService.UploadFile_ServerPath);
        }

        [Fact]
        public void ValidatesDirectory()
        {
            TestableTfsEngine engine = TestableTfsEngine.Create();
            engine.FileSystem.EnsurePath(@"C:\Foo");
            engine.TfsState.TrackFolder("http://tfsUrl", "$/Foo", @"C:\Foo", 1, 2, SourceItemStatus.Unmodified);

            engine.Commit(@"C:\Foo", "message", null);

            Assert.Equal(@"C:\Foo", engine.ValidateDirectoryStructure_Directory);
        }
    }
}